﻿using System;
using System.Collections.Generic;
using MentalAlchemy.Atomics;

namespace MentalAlchemy.Molecules.MachineLearning.GradEAAlg
{
	public class GradEA : EvolutionaryAlgorithm
	{
		protected Mutation mutation;
		private Crossover xover;

		public bool UseHistory
		{
			set
			{
				mutation.UseHistory = value;
				xover.UseHistory = value;
			}
		}

		public float UpdateInertia { get; set; }

		public Crossover CrossoverOperator
		{
			get { return xover; }
			set { xover = value; }
		}

		public GradEA ()
		{
			PopulationCreation = GradEAElements.CreatePopulation;

			mutation = new GaussianMutation(this);
			mutation.UseHistory = false;

			xover = new WeightedCrossover(this);
			xover.UseHistory = false;

			UpdateInertia = Individual.DEFAULT_UPDATE_INERTIA;
		}

		public override void Init(EAParameters parameters)
		{
			base.Init(parameters);

			foreach (Individual ind in popul)
			{
				ind.UpdateInertia = UpdateInertia;
			}
		}

		public override void Continue(EAParameters parameters)
		{
			SetParameters(parameters);

			//
			// validate [parameters].
			if (FitnessFunction != null && !EAElements.ValidateParameters(parameters)) throw new Exception("[EvolutionaryAlgorithm.Run]: Invalid parameters setting or fitness function is undefined.");

			// evaluate current population.
			Evaluate();
			for (CurrentGeneration = 1; CurrentGeneration <= parameters.GenerationsNumber; ++CurrentGeneration)
			{
				//
				// Selection.
				popul = EAElements.TournamentSelection(popul, parameters.TournamentSize, ContextRandom.rand);

				//
				// create children.
				for (int i = 0; i < popul.Count; i += 2)
				{
					// select parent individuals.
					var p = new List<AbstractIndividual>();
					int p1, p2;
					//p.Add(popul[i]);
					//p.Add(popul[i + 1]);
					if (FitnessComparator.IsBetter(popul[i].Fitness, popul[i + 1].Fitness))
					{
						p.Add(popul[i]);
						p.Add(popul[i + 1]);
						p1 = i;
						p2 = i + 1;
					}
					else
					{
						p.Add(popul[i + 1]);
						p.Add(popul[i]);
						p1 = i + 1;
						p2 = i;
					}

					var c = xover.Operate(p);

					if (FitnessComparator.IsBetter(c[0].Fitness, popul[p1].Fitness))
					{
						popul[p1] = c[0];
					}
					if (FitnessComparator.IsBetter(c[1].Fitness, popul[p2].Fitness))
					{
						popul[p2] = c[1];
					}
					//((Individual)popul[i]).Assign((Individual)c[0]);
					//((Individual)popul[i + 1]).Assign((Individual)c[1]);
					//popul[i + 1] = c[1];
				}

				//
				// Mutation.

				// update mutation step.
				var minMutStep = 1e-3f;
				var maxMutStep = 10f;
				//parameters.MutationStep = 0.1f;
				parameters.MutationStep = (float)Math.Sqrt(stats[stats.Count - 1].Variance);
				if (parameters.MutationStep <= minMutStep) { parameters.MutationStep = 1f / (CurrentGeneration); }
				if (parameters.MutationStep < minMutStep) parameters.MutationStep = minMutStep;
				if (parameters.MutationStep > maxMutStep) parameters.MutationStep = maxMutStep;
				stats[stats.Count - 1].AppendData(parameters.MutationStep, "MutationStep");
				for (int i = 0; i < popul.Count; i++)
				{
					var inds = new List<AbstractIndividual>();
					inds.Add(popul[i]);
					var mut = mutation.Operate(new List<AbstractIndividual>(inds));

					//((Individual)popul[i]).Assign((Individual)mut[0]);
					if (FitnessComparator.IsBetter(mut[0].Fitness, popul[i].Fitness))
					{
						popul[i] = mut[0];
					}
				}

				//
				// Update the best individual.
				var tempBest = EAElements.GetBestIndividual(popul);
				if (bestInd == null || FitnessComparator.IsBetter(tempBest.Fitness, bestInd.Fitness))
				{
					bestInd = (Individual)tempBest.Clone();
				}

				CollectStats();
			}

			Evaluate();	// final population evaluation.
		}

		public void SetParameters (EAParameters parameters)
		{
			mutation.SetParameters(parameters);
		}

		public AbstractIndividual SelectAmongIndividuals(AbstractIndividual ind1, AbstractIndividual ind2)
		{
			if (FitnessComparator.IsBetter(ind1.Fitness, ind2.Fitness))
			{
				return ind1;
			}
			else
			{
				return ind2;
			}
		}
	}
}